#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> //getpid
#include <unistd.h> // getpid , alarm
#include <signal.h> //signal
#include <sys/times.h> // times
#include <errno.h>
#include <sys/wait.h>
#include <string.h>

#include "limits.h"
#include "load_jobs.h"
#include "linkedList.h"
#include "SJF.h"
#include "fifo.h"
#include "RR.h"
#include "GanttChart.h"

int ALRM = 0;  // global variable for alarm()
pid_t pw2 = 0; // global variable for sigchild
pid_t child_pid = 0;


void sig_handler(int sig) {

      //  write(STDOUT_FILENO,"ALARM\n",sizeof("ALARM\n"));
        ALRM = 1;
}

void sig_handler2(int sig) {
        //write(STDOUT_FILENO,"SIGCHLD\n",sizeof("SIGCHLD\n"));
        //pw2 = waitpid(child_pid,0,0);
        pw2 = wait(NULL);
}

int main(int argc, char *argv[])
{
    // monitor path
    char *monitor = "./monitor";
    //char *monitor = "/home/test/Desktop/working/bin/Debug/monitor";
    // times function
    clock_t st_cpu, en_cpu;//, en_cpu2;
    struct tms  tmsstart, tmsend;//, tmsend2;
    static long clktck = 0;
    useconds_t usec = 0; // Store PSJF alarm return value
    int alarm_policy = 1;

//    pid_t pw ;
    int prog_done = 0; // number of program that finished
    int pc = 0;  // total program counter
    int strategy = 0;

    struct jobNode *job = NULL;
    struct jobNode *job_not_enqueue = NULL;
    struct jobNode *ready = NULL;
    //struct jobNode *ready = NULL;
    (void) signal(SIGALRM, sig_handler);
    (void) signal(SIGCHLD, sig_handler2);

    if (argc == 3) {
    	if (strcmp(argv[2],"FIFO") == 0)
                    strategy = 0;
            else if (strcmp(argv[2],"SJF") == 0)
                    strategy = 1;
                else if (strcmp(argv[2],"RR") == 0)
                        strategy = 2;
                    else if (strcmp(argv[2],"PSJF") == 0)
                            strategy = 3;
                        else
                            strategy = 4;
    }


    if (argc != 3 || strategy < 0 || strategy > 3) {
    	perror("Scheduler usage: ./scheduler [input filename] [policy]\n[input filename] is the path to the job description file\n[policy] is the scheduling policy which should take the following values : \n     - \"FIFO\" (which means the first-in first-out policy)\n     - \"RR\" (which means the round robin policy)\n     - \"SJF\" (which means the shortest job first policy\n     - \"PSJF\" (which means the preemptive-shortest job first policy\n");
    	exit(-1);
    } else {
        // fetch clock ticks per second first time
        if (clktck == 0)
            if ((clktck = sysconf(_SC_CLK_TCK)) < 0) {
                perror("sysconf error");
                exit(-1);
            }
        // set environment variable
        if(setenv("PATH","/bin:/sbin:/usr/bin:/usr/local/bin",1) != 0) {
            printf("Cannot set PATH env variable\n");
            exit(-1);
        }
        //load jobs from file into linked list
        job = create_job_linked_list(argv[1], &pc);
        job_not_enqueue = job;

        st_cpu = times(&tmsstart); // mark the cpu tick.

        do{

            switch (strategy)
            {
            	case 0:fifo(&ready,&job_not_enqueue,st_cpu,&alarm_policy);
            		break;
                case 1: sjf(&ready,&job_not_enqueue,st_cpu,0,&alarm_policy);
            		break;
                case 2:rr(&ready,&job_not_enqueue,st_cpu,&alarm_policy);
            		break;
                case 3:sjf(&ready,&job_not_enqueue,st_cpu,1,&alarm_policy);
            		break;

            	default: perror("Program internal error : strategy < 0 || strategy > 3\n");
                        exit(-1);
            		break;
            }
        //while(ready!= NULL){
        //    printf("Ready : %d %s %d \n",ready->arrivalTime, ready->command, ready->duration);
        //    ready = ready->ready_next;
        //    }
            if(ready != NULL) {
                if (ready->child_pid == 0) { // new process, need to fork
                    //printf("fork - %s %d\n",ready->command, ready->duration);
                    if ((ready->child_pid = fork()) == 0) { // child process
                        execlp(monitor,monitor,ready->command,NULL);
                        perror("execlp");
                        exit(-1);
                    } else {
                        en_cpu = times(&tmsend);
                        ready->timeline[ready->timelineIndex][0] = (float)(en_cpu - st_cpu)/(float)clktck;
                    }
                } else { // child process already exists , send SIGCONT
                    //if(kill(ready->child_pid,SIGCONT) == -1){
                    //    perror("SIGCONT ");
                    //    exit(-1);
                    //}
                    kill(ready->child_pid,SIGCONT);
                    en_cpu = times(&tmsend);
                    ready->timeline[ready->timelineIndex][0] = (float)(en_cpu - st_cpu)/(float)clktck;
                    //printf("1 SIGCONT %s %d\n",ready->command,ready->duration);
                    }
            }

            if (strategy != 3)
                alarm(alarm_policy);
            else{
                if( usec != 0 ){
                    ualarm(usec,0);
                    //printf("ualarm usec - %ld\n",(long)usec);}
                 }else{
                    ualarm(alarm_policy*999999,0);
                    //printf("ualarm policy - %ld\n",(long)alarm_policy);}
                }
            }

            // 一定pause，因為alarm policy一定＝max run time，一係自己死，一係alarm響
            //if (strategy != 3)
                pause();

            // run infinity 的話個鐘一秒響一次，所以要再用pause，停到佢有sigchld, RR , PSJF 可以加strategy < 2 去停呢個extra pause()
            if (ready!= NULL && ready->runinfinity == 1 && strategy < 2)
                while (pw2 != ready->child_pid)
                    pause();
            //printf("pw2 = %d\n",(int)pw2);
            if (ready != NULL) {
                if (strategy <= 1) { // FIFO & SJF part ; 去到呢到，process一係自己死左，一係要kill佢
                    alarm(0);
                    if (pw2 != ready->child_pid) {
                        ready->timeRemain = ready->timeRemain - alarm_policy;
                        if (ready->timeRemain <= 0 && ready->runinfinity == 0)
                            if(kill(ready->child_pid,SIGTERM) == -1){
                            perror("SIGTERM ");
                            exit(-1);
                        }
                    }
                    ready->timeRemain = 0;
                    en_cpu = times(&tmsend);
                    ready->timeline[ready->timelineIndex][1] = (float)(en_cpu - st_cpu)/(float)clktck;
                    (ready->timelineIndex)++;
                    prog_done++;
                    //printf("0 prog_done++;\n");
                } else if (strategy == 2) { // RR part ; 去到呢到，process一係自己死左，一係要kill佢，一係alarm;
                        kill(ready->child_pid,SIGTSTP);
                        ready->quantum--;
                        alarm(0);
                        en_cpu = times(&tmsend);
                        ready->timeline[ready->timelineIndex][1] = (float)(en_cpu - st_cpu)/(float)clktck;
                        (ready->timelineIndex)++;
                        ready->timeRemain = ready->timeRemain - alarm_policy;

                        if(pw2 == ready->child_pid) {
                                ready->timeRemain = 0;
                                prog_done++;
                        } else if(ready->timeRemain <= 0 && ready->runinfinity == 0){
                                    kill(ready->child_pid,SIGCONT);
                                    kill(ready->child_pid,SIGTERM);
                                    ready->timeRemain = 0;
                                    prog_done++;
                               }
                    } else if(strategy == 3) { // PSJF part
                            kill(ready->child_pid,SIGTSTP);
                            usec = ualarm(0,0);
                            //printf("usec : %ld\n",(long)usec);
                            en_cpu = times(&tmsend);


                            ready->timeline[ready->timelineIndex][1] = (float)(en_cpu - st_cpu)/(float)clktck;
                            ready->ptimeRemain = ready->ptimeRemain-(ready->timeline[ready->timelineIndex][1] - ready->timeline[ready->timelineIndex][0]);
                            //printf("%s timeRemain %f\n",ready->command,ready->ptimeRemain);
                            (ready->timelineIndex)++;
                            if(usec > 0) {
                                if(pw2 == ready->child_pid) {
                                    ready->ptimeRemain = 0;
                                    prog_done++;
                                }
                            } else {
                                if(ready->ptimeRemain <= 0 && ready->runinfinity == 0){
                                    kill(ready->child_pid,SIGCONT);
                                    kill(ready->child_pid,SIGTERM);
                                    ready->ptimeRemain = 0;
                                    prog_done++;
                                }
                            }



                        }
            }
        }while(prog_done != pc);
        //exit this loop when there are no job left ----------------------------------------------
        sleep(1);
        printGanttChart(job, MAX);
        freeList(&job);
    }// correct end of program
    return 0;
}
